import java.util.HashMap;
import java.util.HashSet;
import java.util.Stack;
import Node_Classes.ArCosHNode;
import Node_Classes.ArSinHNode;
import Node_Classes.ArTanHNode;
import Node_Classes.ArcCosNode;
import Node_Classes.ArcSinNode;
import Node_Classes.ArcTanNode;
import Node_Classes.CosHNode;
import Node_Classes.CosNode;
import Node_Classes.DivNode;
import Node_Classes.LgNode;
import Node_Classes.LnNode;
import Node_Classes.MinusNode;
import Node_Classes.NumberNode;
import Node_Classes.PlusNode;
import Node_Classes.PotNode;
import Node_Classes.ProdNode;
import Node_Classes.SinHNode;
import Node_Classes.SinNode;
import Node_Classes.SqrtNode;
import Node_Classes.TanHNode;
import Node_Classes.TanNode;
import Node_Classes.TreeNodeObj;
import Node_Classes.XNode;

/**
 * Die Klasse Funktion ist dafuer zustaendig, dass sie den uebergebenen String
 * entsprechend parsen kann und daraus den korrekten Baum zur Berechnung der
 * x-Werte erstellen kann. ausserdem verfuegt sie ueber eine Methode um zu einem
 * uebergebenen x-Wert den entsprechenden y-wert der Funktion zurueckzuliefern.
 * 
 * Sie erbt von der Klasse Tree, somit besitzt sie alle Methoden 
 * um Elemente in den Binär-Baum einzufuegen.
 * @author c.barsch
 */
public class Funktion extends Tree {

	public static class DoNotPaintException extends RuntimeException{};
	
	public static class FlaseFunctionException extends RuntimeException{};
	
	private boolean z=false;
	private boolean nextPot = false;
	private boolean isAktFunktion=false; //dient der einfachen und jederzeit moeglichen Auswertung, ob es sich hierbei um die zurzeit angew�hlte FUnktion handelt 
	private Stack<Tree> stack;
	private HashMap<String, Class> hm;
	private HashSet<Character> hs;
	
	public Funktion(String s) {
		super();
		stack = new Stack<Tree>();

		// Da die Klasse auch ein Tree ist, kann sie auf den Stack gelegt
		// werden. Der Stack dient dem leichten Parsen von Klammern
		stack.add(this);

		/*
		 * Hier werden die Klassen in einer HashMap mit entsprechendem Key
		 * gespeichert. Dies ist spaeter wichtig um die gewuenschten Node
		 * Klassen einfach zu erstellen
		 */
		hm = new HashMap<String, Class>();
		hm.put("ln", LnNode.class);
		hm.put("lg", LgNode.class);
		hm.put("sin", SinNode.class);
		hm.put("cos", CosNode.class);
		hm.put("tan", TanNode.class);
		hm.put("sqrt", SqrtNode.class);
		hm.put("sinh", SinHNode.class);
		hm.put("cosh", CosHNode.class);
		hm.put("tanh", TanHNode.class);
		hm.put("arcsin", ArcSinNode.class);
		hm.put("arccos", ArcCosNode.class);
		hm.put("arctan", ArcTanNode.class);
		hm.put("arsinh", ArSinHNode.class);
		hm.put("arcosh", ArCosHNode.class);
		hm.put("artanh", TanHNode.class);

		this.hs = new HashSet<Character>();
		hs.add('+');
		hs.add('-');
		hs.add('*');
		hs.add('/');

		
		//System.out.println("Ich uebergebe als Funktion: " + changeString(s));
		try{	
			parseFunktion(changeString(s));	
		}catch (NullPointerException e)
		{
			throw new FlaseFunctionException();
		}catch(NumberFormatException e)
		{	
			throw new FlaseFunctionException();
		}
	}

	public String changeString(String s) {
		int i = 0;
		int kcounter = 0;
		StringBuffer sb=new StringBuffer();
		while (i < s.length()) {
			
			sb.append(s.charAt(i));
			if (s.charAt(i) != '^') {
				i++;
			} else {
				if (s.charAt(i + 1) == '(') {
					i++;
				} else {
					sb.append('(');
					i++;
					while (i != s.length()
							&& (!this.hs.contains(s.charAt(i)) || kcounter != 0)) {
						if (s.charAt(i) == '(') {
							kcounter++;
						} else if (s.charAt(i) == ')') {
							kcounter--;
						}
						sb.append(s.charAt(i));
						i++;
					}
					sb.append(')');
				}
			}
		}
		if(hasToChange(s))
		{
			return changeString(sb.toString());
		}
		return sb.toString();
	}
	
	private boolean hasToChange(String s)
	{
		for(int i=0; i<s.length(); i++)
		{
			if(s.charAt(i)=='^' && s.charAt(i+1)!='(')
			{
				return true;
			}
		}
		
		return false;
	}

	/**
	 * Diese Methode liefert fuer einen uebergebenen x-Wert den entsprechenden
	 * y-Wert der Funktion zurueck
	 */
	public double berechne(double x) throws DoNotPaintException{
		double e=this.getRoot().getErgebnis(x);
		if(Double.isNaN(e) || Double.isInfinite(e))
		{
			throw new DoNotPaintException();
		}
		return e;
	}

	/**
	 * Diese Methode wertet den uebergebenen String zeichenweise aus und
	 * erzeugt einen entsprechenden Baum
	 * 
	 * @param s Ist die Funktion als String. s wird hier geparsed werden
	 */
	private void parseFunktion(String s) {
		// Der StringBuffer dient fuer das speichern bzw. erkennen von der
		// trigon., Wurzel- und Logarithmusfunktionen
		StringBuffer sb = new StringBuffer();
		// In number werden alle Zahlen als String abgespeichert
		String number = "";
		char zeichen;
		// Durchlaufen des gesamten Strings Zeichen fuer Zeichen
		for (int i = 0; i < s.length(); i++) {
			zeichen = s.charAt(i);
			// alle Leerzeichen werden uebersprungen
			if (zeichen == ' ') {
				continue;
			}
			// Jede Zahl, e oder x wird gespeichert und die Schleife wird erneut
			// durchlaufen
			if (Character.isDigit(zeichen) || zeichen == 'e' || zeichen == 'x'
					|| zeichen == '.') {
				number += zeichen;
			}

			/*
			 * falls ein Buchstabe eingegeben wird, wird er in sb gespeichert
			 * und es wird ueberprueft, ob das bisher so entstandene Wort
			 * bekannt ist. Falls ja, so wird der entsprechende Knoten
			 * angehangen und sb geleert, falls nicht beginnt die Schleife von
			 * vorne
			 */
			else if (Character.isLetter(zeichen)) {
				sb.append(zeichen);

				if (sb.toString().toLowerCase().equals("pi")) {
					number += "pi";
					sb = new StringBuffer();
				} else if (hm.containsKey(sb.toString().toLowerCase())) {
					try {
						// Hier wird ein Knoten aus der entsprechenden Klasse in
						// der HashMap erzeugt
						stack.lastElement().addElement(
								(TreeNodeObj) hm.get(
										sb.toString().toLowerCase())
										.newInstance(), false);
						// damit auch auf die funk als vater gegangen wird,
						// aehnlich noetig wie bei ^
						this.nextPot = true;
						sb = new StringBuffer();
					} catch (InstantiationException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}
				}

			} else {
				switch (zeichen) {
				case '+':
					;
				case '-':
					;
				case '*':
					;
				case '/':
					;
				case '^':
					this.fuegeInTreeZeichenEin(stack.lastElement(), number,
							zeichen);
					number = "";
					break;

				case '(':
					// wenn eine Klammer geoeffnet wird, wird ein neuer Baum
					// angelegt und alle Operationen finden bis die Klammer
					// geschlossen wird auf diesem Baum statt
					stack.push(new Tree());

					// falls diese Klammerung eine Potenz ist, muss es in dem
					// Tree vermerkt werden
					stack.lastElement().set_isPot(this.nextPot);
					this.nextPot = false;
					break;

				case ')':
					// falls klammer geschlossen wird und noch ein element
					// beinhaltet wird es angehangen
					if (!number.equals("")) {
						stack.lastElement().addElement(addNumber(number),
								this.nextPot);
						this.nextPot = false;

						number = "";
					}

					// Es wird geprueft ob die Klammerung fuer ein ^ ist
					boolean potTree = stack.lastElement().get_isPot();

					// fuege den erstellten Baum an den Baum hinter ihm auf dem
					// Stack an
					(stack.get(stack.size() - 2)).addElement(stack.pop()
							.getRoot(), this.nextPot);
					this.nextPot = false;

					// Wenn der angefuegte Baum fuer ein ^ bestimmt war, muss
					// currentNode von dem angefuegten Element auf den Vater
					// bzw. dem ^-Node wieder gestellt werden
					if (potTree) {
						stack.lastElement().CNasFather();
					}
					break;

				}
			}
		}
		// Falls!! noch eine Zahl vorhanden ist, muss sie noch angehangen
		// werden. Bsp: bei 5-3 wird im oberen Abschnitt ein Baum mit 5-
		// erzeugt, aber die 3 wird nicht angehangen, aber gelesen. Daher muss
		// sie hier, falls eine Zahl gelesen wurde, angehangen werden
		if (!number.equals("")) {
			this.addElement(addNumber(number), this.nextPot);
			this.nextPot = false;
		}
		
	}

	/**
	 * Die Methode dient dazu, an den entsprechenden Tree t, die entsprechende
	 * Zahl (aus number) und den entsprechenden Node (aus zeichen) anzuhaengen
	 */
	private void fuegeInTreeZeichenEin(Tree t, String number, char zeichen) {
		// wenn in number noch etwas drin steht, haenge es an
		// Jedes mal wenn etwas angehangen wird, wird nextPot=false gesetzt. Das
		// ist wichtig falls ein ^ Node zuvor angehaengt wurde
		if (!number.equals("")) {
			t.addElement(addNumber(number), this.nextPot);
			this.nextPot = false;
		}
		// falls number nicht belegt ist und ein minus Node angehangen werden
		// soll, wird eine 0 hinzugefuegt (wichtig damit z.b.-3 funktioniert)
		else if (zeichen == '-') {
			t.addElement(new NumberNode(0), this.nextPot);
			this.nextPot = false;
		}
		// Je nach Zeichen, muss der das entsprechende Node-Objekt angehangen
		// werden
		if (zeichen == '+') {
			t.addAsRoot(new PlusNode());
		} else if (zeichen == '-') {
			t.addAsRoot(new MinusNode());
		} else if (zeichen == '*') {
			t.addAsFather(new ProdNode());
		} else if (zeichen == '/') {
			t.addAsFather(new DivNode());
		} else if (zeichen == '^') {
			t.addAsFather(new PotNode());
			this.nextPot = true;
		}
	}

	/**
	 * Diese Methode liefert fuer den String number (indem entweder nur zahlen,
	 * oder pi oder e drinstehen) ein entsprechendes NodeObjekt
	 */
	private TreeNodeObj addNumber(String number) {
		if (number.equals("e")) {
			return new NumberNode(Math.E);
		} else if (number.equals("pi")) {
			return new NumberNode(Math.PI);
		} else if (number.equals("x")) {
			return new XNode();
		} else {
			return new NumberNode(Double.valueOf(number));
		}
	}
	public boolean isAktFunktion(){
		return isAktFunktion;
	}
	public void toogleAktFunktion(){
		this.isAktFunktion = !this.isAktFunktion;
	}

}
